using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using LambdaBuilder.ExpressionTree;
using LambdaBuilder.LambdaAttribute;
using LambdaBuilder.ValueObjects;

namespace LambdaBuilder.Resolver
{
	public class LambdaResolver
	{
		public static string GetColumnName<T>(Expression<Func<T, object>> selector)
		{
			return LambdaResolver.GetColumnName(LambdaResolver.GetMemberExpression(selector.Body));
		}

		public static List<string> GetColumnNames<T>(Expression<Func<T, object>> selector)
		{
			List<string> list = new List<string>();
			if (selector.Body.NodeType == ExpressionType.New)
			{
				NewExpression newExpression = selector.Body as NewExpression;
				foreach (MemberInfo item in newExpression.Members!)
				{
					list.Add(item.Name);
				}
				return list;
			}
			list.Add(LambdaResolver.GetColumnName(selector));
			return list;
		}

		public static string GetColumnName(Expression expression)
		{
			MemberExpression memberExpression = LambdaResolver.GetMemberExpression(expression);
			LamPropertyAttribute lamPropertyAttribute = memberExpression.Member.GetCustomAttributes(inherit: false).OfType<LamPropertyAttribute>().FirstOrDefault();
			if (lamPropertyAttribute != null)
			{
				return lamPropertyAttribute.Name;
			}
			return memberExpression.Member.Name;
		}

		public static string GetTableName<T>()
		{
			return LambdaResolver.GetMemberName(typeof(T));
		}

		public static string GetMemberName(Type type)
		{
			LamClassAttribute lamClassAttribute = type.GetCustomAttributes(inherit: false).OfType<LamClassAttribute>().FirstOrDefault();
			if (lamClassAttribute != null)
			{
				return lamClassAttribute.Name;
			}
			return type.Name;
		}

		private static string GetMemberName(MemberExpression expression)
		{
			return LambdaResolver.GetMemberName(expression.Member.DeclaringType);
		}

		private static BinaryExpression GetBinaryExpression(Expression expression)
		{
			if (expression is BinaryExpression)
			{
				return expression as BinaryExpression;
			}
			throw new ArgumentException("Binary expression expected");
		}

		private static MemberExpression GetMemberExpression(Expression expression)
		{
			return expression.NodeType switch
			{
				ExpressionType.MemberAccess => expression as MemberExpression, 
				ExpressionType.Convert => LambdaResolver.GetMemberExpression((expression as UnaryExpression).Operand), 
				_ => throw new ArgumentException("Member expression expected"), 
			};
		}

		public Node ResolveQuery<T>(Expression<Func<T, object>> expression)
		{
			dynamic val = ResolveQuery((dynamic)expression.Body);
			return val;
		}

		private Node ResolveQuery(ConstantExpression constantExpression)
		{
			return new ValueNode
			{
				Value = constantExpression.Value
			};
		}

		private Node ResolveQuery(UnaryExpression unaryExpression)
		{
			SingleOperationNode singleOperationNode = new SingleOperationNode();
			singleOperationNode.Operator = unaryExpression.NodeType;
			singleOperationNode.Child = ResolveQuery((dynamic)unaryExpression.Operand);
			return singleOperationNode;
		}

		private Node ResolveQuery(BinaryExpression binaryExpression)
		{
			OperationNode operationNode = new OperationNode();
			operationNode.Left = ResolveQuery((dynamic)binaryExpression.Left);
			operationNode.Operator = binaryExpression.NodeType;
			operationNode.Right = ResolveQuery((dynamic)binaryExpression.Right);
			return operationNode;
		}

		private Node ResolveQuery(MethodCallExpression callExpression)
		{
			if (Enum.TryParse<LikeMethod>(callExpression.Method.Name, ignoreCase: true, out var result))
			{
				MemberExpression expression = callExpression.Object as MemberExpression;
				string value = (string)this.GetExpressionValue(callExpression.Arguments.First());
				return new LikeNode
				{
					MemberNode = new MemberNode
					{
						MemberName = LambdaResolver.GetMemberName(expression),
						FieldName = LambdaResolver.GetColumnName(callExpression.Object)
					},
					Method = result,
					Value = value
				};
			}
			object value2 = this.ResolveMethodCall(callExpression);
			return new ValueNode
			{
				Value = value2
			};
		}

		private Node ResolveQuery(MemberExpression memberExpression, MemberExpression rootExpression = null)
		{
			rootExpression = rootExpression ?? memberExpression;
			switch (memberExpression.Expression!.NodeType)
			{
			case ExpressionType.Parameter:
				return new MemberNode
				{
					MemberName = LambdaResolver.GetMemberName(rootExpression),
					FieldName = LambdaResolver.GetColumnName(rootExpression)
				};
			case ExpressionType.MemberAccess:
				return this.ResolveQuery(memberExpression.Expression as MemberExpression, rootExpression);
			case ExpressionType.Call:
			case ExpressionType.Constant:
				return new ValueNode
				{
					Value = this.GetExpressionValue(rootExpression)
				};
			default:
				throw new ArgumentException("Expected member expression");
			}
		}

		private Node ResolveQuery(NewExpression newExpression)
		{
			List<NodeParamInfo> list = new List<NodeParamInfo>();
			for (int i = 0; i < newExpression.Members!.Count; i++)
			{
				list.Add(new NodeParamInfo
				{
					Name = newExpression.Members![i].Name,
					Value = (newExpression.Arguments[i] as ConstantExpression)?.Value
				});
			}
			return new NewExpNode
			{
				Params = list
			};
		}

		private object GetExpressionValue(Expression expression)
		{
			switch (expression.NodeType)
			{
			case ExpressionType.Constant:
				return (expression as ConstantExpression).Value;
			case ExpressionType.Call:
				return this.ResolveMethodCall(expression as MethodCallExpression);
			case ExpressionType.MemberAccess:
			{
				MemberExpression memberExpression = expression as MemberExpression;
				object expressionValue = this.GetExpressionValue(memberExpression.Expression);
				return ResolveValue((dynamic)memberExpression.Member, expressionValue);
			}
			default:
				throw new ArgumentException("Expected constant expression");
			}
		}

		private object ResolveMethodCall(MethodCallExpression callExpression)
		{
			object[] array = callExpression.Arguments.Select(new Func<Expression, object>(GetExpressionValue)).ToArray();
			object obj = ((callExpression.Object != null) ? this.GetExpressionValue(callExpression.Object) : array.First());
			return callExpression.Method.Invoke(obj, array);
		}

		private object ResolveValue(PropertyInfo property, object obj)
		{
			return property.GetValue(obj, null);
		}

		private object ResolveValue(FieldInfo field, object obj)
		{
			return field.GetValue(obj);
		}

		private void ResolveQuery(Expression expression)
		{
			throw new ArgumentException($"The provided expression '{expression.NodeType}' is currently not supported");
		}
	}
}
